// 
// Bachelor of Software Engineering
// Media Design School
// Auckland
// New Zealand
//
// (c) 2005 - 2014 Media Design School
//
// File Name	: GameApp.cpp
// Desrciption	: CGameApp implementation file.
// Author		: Hayden Asplet.
// Mail			: hayden.asplet@mediadesignschool.com
//

// PCH
#include "GameStd.h"

// Library Includes
#include <time.h>	// For time(0)

// This Include
#include "GameApp.h"

// Local Includes

CGameApp* g_pApp = NULL;

CGameApp::CGameApp()
{
	g_pApp = this;					// This instance is the app of the game.
	m_pGame = NULL;			

	m_bIsRunning = false;			// The game is not running yet.
	m_bShowFrameRate = false;
	m_screenSize = CPoint(0,0);
	m_fDeltaTime = 0;
	m_iFrameRate = 0;
}

CGameApp::~CGameApp()
{

}

CPoint CGameApp::VGetScreenSetupSize()
{
	return CPoint(g_kiSCREEN_WIDTH, g_kiSCREEN_HEIGHT);
}

DWORD CGameApp::VGetWindowStyle()
{
	return WS_OVERLAPPEDWINDOW | WS_VISIBLE;
}

HWND CGameApp::GetHwnd() const
{
	return m_hWnd;
}

HINSTANCE CGameApp::GetInstance() const
{
	return m_hInstance;
}

bool CGameApp::VInitialize(HINSTANCE _hInstance, LPSTR _lpCmdLine, HWND _hWnd, int _iFrameRate)
{
	m_screenSize = VGetScreenSetupSize();
	m_iFrameRate = _iFrameRate;
	m_hInstance = _hInstance;
	
	WNDCLASSEX winClass;

	winClass.cbSize = sizeof(WNDCLASSEX);
	winClass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
	winClass.lpfnWndProc = MsgProc;
	winClass.cbClsExtra = 0;
	winClass.cbWndExtra = 0;
	winClass.hInstance = m_hInstance;
	winClass.hIcon = VGetIcon();
	winClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	winClass.hbrBackground = static_cast<HBRUSH>(GetStockObject(BLACK_BRUSH));
	winClass.lpszMenuName = NULL;
	winClass.lpszClassName = WINDOW_CLASS_NAME;
	winClass.hIconSm = VGetIcon();

	// Register the window class.
	if(!RegisterClassEx(&winClass)) {
	// Failed to register.
		return false;
	}

	// Create the window.
	m_hWnd = CreateWindowEx(NULL,									// Extended style.
							WINDOW_CLASS_NAME,						// Class.
							VGetTitle(),							// Title
							VGetWindowStyle(),						// Flags. 
							0, 0,									// Initial x, y.
							m_screenSize.m_iX, m_screenSize.m_iY,	// Initial width, height.
							NULL,									// Handle to parent.
							NULL,									// Handle to menu.
							m_hInstance,							// Instance of this application.
							NULL);									// Extra creation parameters.

	if(!m_hWnd) {
	// Window wasn't created successfully.
		return false;
	}

	// This requests a minimum resolution for the Sleep() function to be a single millisecond.
	timeBeginPeriod(1);

	// Seed the random number generator.
	srand(static_cast<unsigned int>(time(0)));

	m_pGame = VCreateGame();	// Creates and initializes the game.

	if(!m_pGame) {
		return false;
	}

	// Create the renderer.
	m_pBackBuffer = new CBackBuffer();

	if(!m_pBackBuffer) {
		return false;
	}

	// Initialize renderer.
	if(!m_pBackBuffer->Init(m_hWnd, m_screenSize.m_iX, m_screenSize.m_iY)) {
	// Failed to initialize renderer.
		return false;
	}

	m_bIsRunning = true;	// The game is now running.

	// Return initialization succeeded.
	return true;
}

LRESULT CALLBACK CGameApp::MsgProc(HWND _hWnd, UINT _msg, WPARAM _wParam, LPARAM _lParam)
{
	switch(_msg) {
		case WM_CREATE: {
			// Do initialization here...

			// Return success.
			return 0;
			break;
		}

		case WM_COMMAND: {
			break;
		}

		case WM_PAINT: {
			HDC hdc;
			PAINTSTRUCT ps;

			hdc = BeginPaint(_hWnd, &ps);

			// Do painting stuff here...

			EndPaint(_hWnd, &ps);

			// Return success.
			return 0;
			break;
		}
		
		case WM_SIZE: {
			// Get the new screen size.
			g_pApp->UpdateScreenSize(CPoint(LOWORD(_lParam), HIWORD(_lParam)));

			// Let windows handle the rest.
			break;
		}

		case WM_CLOSE: {
			return g_pApp->OnClose();
			break;
		}

		// User Input
		case WM_KEYDOWN:		// Fall through.
		case WM_KEYUP:			// Fall through.
		case WM_MOUSEMOVE:		// Fall through.
		case WM_LBUTTONDOWN:	// Fall through.
		case WM_LBUTTONUP:		// Fall through.
		case WM_RBUTTONDOWN:	// Fall through.
		case WM_RBUTTONUP: {
			if(g_pApp->m_pGame) {
			// The game logic is not NULL.
				// Get the game logic.
				CGameLogic* pGame = g_pApp->m_pGame;

				CAppMsg msg;
				msg.m_hWnd = _hWnd;
				msg.m_lParam = _lParam;
				msg.m_wParam = _wParam;
				msg.m_uMsg = _msg;

				if(pGame) {
					if(pGame->VMsgProc(msg)) {
						// Return success.
						return 0;
					}
					else {
						break;
					}
				}
				else {
					// Couldn't handle message.
					break;
				}
			}
			break;
		}

		default: {
			break;
		}

	}

	// Give control back windows to process messages that we did not take care of.
	return DefWindowProc(_hWnd, _msg, _wParam, _lParam); 
}

void CGameApp::Update()
{
	float fStartTime = static_cast<float>(timeGetTime());

	// Clear the display, ready for new frame.
	m_pBackBuffer->Clear();

	if(m_pGame) {
		// Update the game logic.
		m_pGame->VUpdate(m_fDeltaTime);
	}

	Render();

	/*
	// Get the time of this tick.
	float fTickTime = static_cast<float>(timeGetTime() - fStartTime);

	// Get the amount of milliseconds to sleep for to match frame rate.
	int iSleepTime = static_cast<int>((g_kiONE_SECOND / m_iFrameRate) - fTickTime);

	// Fix the time step.
	//while(static_cast<float>(timeGetTime() - fStartTime) < static_cast<float>(g_kiONE_SECOND / m_iFrameRate)) {
	//}

	//if(fTickTime < g_kiONE_SECOND / m_iFrameRate) {
	//	Sleep(iSleepTime);
	//}*/

	while(timeGetTime() - fStartTime < g_kiONE_SECOND / g_kiFRAMES_PER_SEC) {
		Sleep(1);
	}

	m_fDeltaTime = (static_cast<float>(timeGetTime() - fStartTime)) / g_kiONE_SECOND;
}

void CGameApp::Render()
{
	// Render the views.
	m_pGame->Render(m_fDeltaTime);

#ifdef _DEBUG
	if(m_bShowFrameRate) {
		std::string strFrameRate = std::to_string(static_cast<long long>(1 / m_fDeltaTime));
		RenderText(strFrameRate, 0, 0, g_kWHITE, 24, "Mircrosoft Sans Serif Regular");
	}
#endif

	// Display frame to screen.
	m_pBackBuffer->Render();
}

LRESULT CGameApp::OnClose()
{
	m_bIsRunning = false;

	// Perform clean up.
	if(m_pBackBuffer) {
		delete m_pBackBuffer;
	}

	if(m_pGame) {
		delete m_pGame;
	}

	// Clear the minimum timer resolution.
	timeEndPeriod(1);

	// Kill the application
	DestroyWindow(m_hWnd);
	PostQuitMessage(0);

	// Return success.
	return 0;
}

CSprite CGameApp::LoadSprite(int _id)
{
	HBITMAP hBitmap = LoadBitmap(m_hInstance, MAKEINTRESOURCE(_id));

	if(!hBitmap) {
		std::cerr << "ERROR: Failed to load bitmap from resource id \"" << _id <<  "\" ";
		std::cerr << "with error: " << GetLastError() << std::endl;
		return CSprite();
	}
	else {
		return CSprite(hBitmap);
	}
}

CSprite CGameApp::LoadSprite(const std::string& _krstrFileName)
{
	std::wstring wsFileName;
	wsFileName.append(_krstrFileName.begin(), _krstrFileName.end());

	HBITMAP hBitmap = static_cast<HBITMAP>(LoadImage(NULL, wsFileName.c_str(), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE));

	if(!hBitmap) {
		std::cerr << "ERROR: Failed to load bitmap from file \"" << _krstrFileName <<  "\" ";
		std::cerr << "with error: " << GetLastError() << std::endl;
		return CSprite();
	}
	else {
		return CSprite(hBitmap);
	}
}

void CGameApp::RenderSprite(const CSprite& _krSprite, const CPoint& _krPosition, int _iFrame)
{
	// Get the mask of the frame.
	CRect mask = _krSprite.GetMask(_iFrame);

	// Create the blend functions 
	BLENDFUNCTION BlendFunction;
	BlendFunction.BlendOp = AC_SRC_OVER;
	BlendFunction.BlendFlags = 0;
	BlendFunction.SourceConstantAlpha = g_kiOPAQUE;
	BlendFunction.AlphaFormat = AC_SRC_ALPHA;

	SelectObject(m_pBackBuffer->GetDCMem(), _krSprite.GetBitmap());

	// Blit the sprite to the screen.
	AlphaBlend(m_pBackBuffer->GetDCBuffer(), 
			  _krPosition.m_iX, _krPosition.m_iY, 
			  mask.m_iWidth, mask.m_iHeight, 
			  m_pBackBuffer->GetDCMem(), 
			  mask.m_iX, mask.m_iY, 
			  mask.m_iWidth, mask.m_iHeight, 
			  BlendFunction);
}

void CGameApp::RenderSprite(const CSprite& _krSprite, int _iX, int _iY, int _iFrame)
{
	// Get the mask of the frame.
	CRect mask = _krSprite.GetMask(_iFrame);

	// Create the blend functions 
	BLENDFUNCTION BlendFunction;
	BlendFunction.BlendOp = AC_SRC_OVER;
	BlendFunction.BlendFlags = 0;
	BlendFunction.SourceConstantAlpha = g_kiOPAQUE;
	BlendFunction.AlphaFormat = AC_SRC_ALPHA;

	SelectObject(m_pBackBuffer->GetDCMem(), _krSprite.GetBitmap());

	// Blit the sprite to the screen.
	AlphaBlend(m_pBackBuffer->GetDCBuffer(), 
				_iX, _iY, 
				mask.m_iWidth, mask.m_iHeight, 
				m_pBackBuffer->GetDCMem(), 
				mask.m_iX, mask.m_iY, 
				mask.m_iWidth, mask.m_iHeight, 
				BlendFunction);
}

void CGameApp::RenderText(const std::string& _krstrText, int _iX, int _iY, TColour _colour, int _iHeight, const std::string& _krstrFont)
{
	std::wstring wsText;
	std::wstring wsFont;

	// Convert string to wide string.
	wsText.append(_krstrText.begin(), _krstrText.end());
	wsFont.append(_krstrFont.begin(), _krstrFont.end());

	// Create a new font.
	HFONT hFont = CreateFont(_iHeight, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, wsFont.c_str());

	// Select the font.
	HFONT hOldFont = static_cast<HFONT>(SelectObject(m_pBackBuffer->GetDCBuffer(), hFont));

	// Set the mode and colour
	SetBkMode(m_pBackBuffer->GetDCBuffer(), TRANSPARENT);
	SetTextColor(m_pBackBuffer->GetDCBuffer(), _colour);

	// Render Text.
	TextOut(m_pBackBuffer->GetDCBuffer(), _iX, _iY, wsText.c_str(), wcslen(wsText.c_str()));

	// Add the old font back.
	SelectObject(m_pBackBuffer->GetDCBuffer(), hOldFont);

	DeleteObject(hFont);
}

void CGameApp::UpdateScreenSize(const CPoint& _screenSize)
{
	m_screenSize = _screenSize;

	if(m_pBackBuffer) {
		m_pBackBuffer->UpdateScreenSize(m_screenSize);
	}
}

CPoint CGameApp::GetScreenSize() const
{
	return m_screenSize;
}

bool CGameApp::IsRunning() const
{
	return m_bIsRunning;
}

void CGameApp::SetFrameRateShowing(bool _bIsShowing)
{
	m_bShowFrameRate = _bIsShowing;
}

